package com.wwg.config.ds;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.wwg.util.date.DateUtil;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;

/**
 * 类描述:数据库操作性能拦截器,记录耗时
 */

@Intercepts(value = {
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class})})
public class SqlStatementInterceptor implements Interceptor {
    private static Logger logger = LoggerFactory.getLogger(SqlStatementInterceptor.class);
    
	@SuppressWarnings("rawtypes")
	@Autowired
	private RedisTemplate redisTemplate;

    @SuppressWarnings("unchecked")
	@Override
    public Object intercept(Invocation invocation) throws Throwable {
        long time = 0;
        JSONObject timeObj = new JSONObject();
        try {
            Object returnValue;
            long start = System.currentTimeMillis();
            timeObj.put("startTime", DateUtil.getCurrentDate(DateUtil.FORMAT_YYYY_MM_DD_HH_MM_SS_SSS));
            returnValue = invocation.proceed();
            long end = System.currentTimeMillis();
            time = end - start;
            
            return returnValue;
        } finally {
            try {
                final Object[] args = invocation.getArgs();
                // 获取原始的ms
                MappedStatement ms = (MappedStatement)args[0];
                Object parameter = null;
                // 获取参数，if语句成立，表示sql语句有参数，参数格式是map形式
                if (invocation.getArgs().length > 1) {
                    parameter = invocation.getArgs()[1];
                }
                // 获取到节点的id,即sql语句的id
                String sqlId = ms.getId();
                BoundSql boundSql = ms.getBoundSql(parameter); // BoundSql就是封装myBatis最终产生的sql类
                Configuration configuration = ms.getConfiguration(); // 获取节点的配置
                String sql = getSql(configuration, boundSql, sqlId, time); // 获取到最终的sql语句
                logger.info(sql);
                timeObj.put("sql", sql);
                timeObj.put("sqlName", sqlId);
                timeObj.put("totalTime", (time) + " 毫秒");
//                logger.debug("timeObj = {}",timeObj);
                
                String threadName = Thread.currentThread().getName();
                timeObj.put("thread", threadName);
                timeObj.put("endTime", DateUtil.getCurrentDate(DateUtil.DATE_FORMAT_YYYYMMDDHHMMSSSSS));
//                JSONArray jsonArray = TimeCountingAspect.get(threadName);
        		Object redisModel =  this.redisTemplate.opsForValue().get(threadName);
        		JSONArray jsonArray = null;
        		if(redisModel==null){
        			jsonArray = new JSONArray();
        		}else
        		{
        			jsonArray = (JSONArray) redisModel;
        		}
        		jsonArray.add(timeObj);
        		redisTemplate.opsForValue().set(threadName, jsonArray, 300l, TimeUnit.SECONDS);
                
            } catch (Exception e) {
                logger.error("拦截sql处理问题：" + e.getMessage(),e);
            }
        }
    }

    // 封装了一下sql语句，使得结果返回完整xml路径下的sql语句节点id + sql语句
    public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
        String sql = showSql(configuration, boundSql);
        StringBuilder str = new StringBuilder(100);
        str.append("\n\n============================begin================================\n\n");
        str.append("jfpt-czb || "+DateUtil.getCurrentDate(DateUtil.FORMATE_YYYY_MM_DD_HH_MM_SS)+" || INFO || ");
        str.append(sqlId);
        str.append(" || ["+Thread.currentThread().getName()+"] || ");
        str.append("耗时【");
        str.append(time);
        str.append("】毫秒  || ");
        str.append(sql);
        str.append("\n\n=============================end=================================\n\n");
        return str.toString();
    }

    /*
     * <br> *如果参数是String，则添加单引号， 如果是日期，则转换为时间格式器并加单引号；
     * 对参数是null和不是null的情况作了处理<br>
     */
    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT,
                    Locale.CHINA);
            value = "'" + formatter.format(obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }

        }
        return value;
    }

    // 进行？的替换
    public static String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject(); // 获取参数
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " "); // sql语句中多个空格都用一个空格代替
        if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); // 获取类型处理器注册器，类型处理器的功能是进行java类型和数据库类型的转换<br>
            // //
            // 如果根据parameterObject.getClass(）可以找到对应的类型，则替换
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));

            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);// MetaObject主要是封装了originalObject对象，提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName); // 该分支是动态sql
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    } else {
                        sql = sql.replaceFirst("\\?", "缺失");
                    } // 打印出缺失，提醒该参数缺失并防止错位
                }
            }
        }
        return sql;
    }


    @Override
    public Object plugin(Object arg0) {
        return Plugin.wrap(arg0, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

}
